﻿//Copyright © 2014 Sony Computer Entertainment America LLC. See License.txt.

using System;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Threading;
using System.Windows.Threading;

namespace Sce.Atf.Wpf.Applications
{
    /// <summary>
    /// Wrapper class that implements ISynchronizeInvoke for WPF applications.</summary>
    /// <remarks>
    /// The ISynchronizeInvoke and DispatcherAsyncResultAdapter implementations were provided by Rob Paveza, from this post:
    /// http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx </remarks>
    [Export(typeof(IInitializable))]
    [Export(typeof(ISynchronizeInvoke))]
    public class SynchronizeInvoke : ISynchronizeInvoke, IInitializable
    {
        #region IInitializable

        /// <summary>
        /// Complete initialization for the component</summary>
        public void Initialize()
        {
            m_uiDispatcher = Dispatcher.CurrentDispatcher;
        }

        #endregion

        #region ISynchronizeInvoke

        /// <summary>
        /// Asynchronously executes the delegate on the thread that created this object.</summary>
        /// <param name="method">A Delegate to a method that takes parameters of the same number and
        /// type that are contained in args. </param>
        /// <param name="args">An array of type Object to pass as arguments to the given method. This 
        /// can be null if no arguments are needed. </param>
        /// <returns>An IAsyncResult interface that represents the asynchronous operation started by calling this method.</returns>
        public IAsyncResult BeginInvoke(Delegate method, object[] args)
        {
            if (args != null && args.Length > 1)
            {
                object[] argsSansFirst = GetArgsAfterFirst(args);
                DispatcherOperation op = m_uiDispatcher.BeginInvoke(DispatcherPriority.Normal, method, args[0], argsSansFirst);
                return new DispatcherAsyncResultAdapter(op);
            }
            else
            {
                if (args != null)
                {
                    return new DispatcherAsyncResultAdapter(m_uiDispatcher.BeginInvoke(DispatcherPriority.Normal, method, args[0]));
                }
                else
                {
                    return new DispatcherAsyncResultAdapter(m_uiDispatcher.BeginInvoke(DispatcherPriority.Normal, method));
                }
            }
        }

        /// <summary>
        /// Waits until the process started by calling BeginInvoke completes, and then returns the 
        /// value generated by the process.</summary>
        /// <param name="result">An IAsyncResult interface that represents the asynchronous operation 
        /// started by calling BeginInvoke. </param>
        /// <returns>An Object that represents the return value generated by the asynchronous operation.</returns>
        public object EndInvoke(IAsyncResult result)
        {
            DispatcherAsyncResultAdapter res = result as DispatcherAsyncResultAdapter;
            if (res == null)
                throw new InvalidCastException();


            while (res.Operation.Status != DispatcherOperationStatus.Completed || res.Operation.Status == DispatcherOperationStatus.Aborted)
            {
                Thread.Sleep(50);
            }

            return res.Operation.Result;
        }

        /// <summary>
        /// Synchronously executes the delegate on the thread that created this object and 
        /// marshals the call to the creating thread.</summary>
        /// <param name="method">A Delegate that contains a method to call, in the context of 
        /// the thread for the control. </param>
        /// <param name="args">An array of type Object that represents the arguments to pass to 
        /// the given method. This can be null if no arguments are needed. </param>
        /// <returns>An Object that represents the return value from the delegate being invoked, 
        /// or null if the delegate has no return value.</returns>
        public object Invoke(Delegate method, object[] args)
        {
            if (args != null && args.Length > 1)
            {
                object[] argsSansFirst = GetArgsAfterFirst(args);
                return m_uiDispatcher.Invoke(DispatcherPriority.Normal, method, args[0], argsSansFirst);
            }
            else
            {
                if (args != null)
                {
                    return m_uiDispatcher.Invoke(DispatcherPriority.Normal, method, args[0]);
                }
                else
                {
                    return m_uiDispatcher.Invoke(DispatcherPriority.Normal, method);
                }

            }
        }

        /// <summary>
        /// Gets a value indicating whether the caller must call Invoke when calling 
        /// an object that implements this interface.</summary>
        public bool InvokeRequired
        {
            get { return m_uiDispatcher.Thread != Thread.CurrentThread; }
        }

        #endregion

        private static object[] GetArgsAfterFirst(object[] args)
        {
            object[] result = new object[args.Length - 1];
            Array.Copy(args, 1, result, 0, args.Length - 1);
            return result;
        }

        private class DispatcherAsyncResultAdapter : IAsyncResult
        {
            public DispatcherAsyncResultAdapter(DispatcherOperation operation)
            {
                m_op = operation;
            }

            public DispatcherAsyncResultAdapter(DispatcherOperation operation, object state)
                : this(operation)
            {
                m_state = state;
            }

            public DispatcherOperation Operation
            {
                get { return m_op; }
            }

            #region IAsyncResult Members

            public object AsyncState
            {
                get { return m_state; }
            }

            public WaitHandle AsyncWaitHandle
            {
                get { return null; }
            }

            public bool CompletedSynchronously
            {
                get { return false; }
            }

            public bool IsCompleted
            {
                get { return m_op.Status == DispatcherOperationStatus.Completed; }
            }

            #endregion

            private DispatcherOperation m_op;
            private object m_state;
        }

        private Dispatcher m_uiDispatcher;
    }
}
